package com.john.market.classes;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import android.os.Handler;

import com.john.market.interfaces.IUpdatable;
import com.john.market.interfaces.IBuyer;
import com.john.market.interfaces.ISeller;
import com.john.market.interfaces.OnRefreshListener;
import com.john.market.interfaces.Storage;

/**
 * Product manager for the market
 *
 */
public class ProductManager implements IBuyer, ISeller {
	// List of update listeners 
	private List<IUpdatable> mListeners = new ArrayList<IUpdatable>();
	
	// Handler for threads
	private Handler mHandler = new Handler();
	
	// Delays for threads
	private final int DELAY_SHORT = 3000; 
	private final int DELAY_LONG = 5000;
	
	// Storage object
	private Storage mStorage = null;
	
	// Refresh listener
	private OnRefreshListener mRefreshListener = new OnRefreshListener() {
		@Override
		public void refresh(Product product) {
			// Notify all the update listeners
			for (IUpdatable listener : mListeners) {
				listener.onUpdated(product);
			}
		}
	};
	
	/**
	 * Runnable class for buying 
	 *
	 */
	private class RunnableBuy implements Runnable {
		// Link to the global mListeners
		private List<IUpdatable> mListeners = ProductManager.this.mListeners;
		// Link to the global mStorage
		private Storage mStorage = ProductManager.this.mStorage;
		// Processed product
		private Product mProduct = null;
		//
		private int mRemain = 0;
		
		/**
		 * Creates new runnable object
		 * 
		 * @param product
		 * Processed product
		 * 
		 */
		public RunnableBuy(Product product) {
			this.mProduct = new Product(product);
		}
		
		@Override
		public synchronized void run() {
			// Simulation of long process
			try {
				Thread.sleep(DELAY_SHORT);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			// Getting product amount
			int amount = mProduct.getAmount();
			
			// Decrementing product amount
			mProduct.setAmount(amount - 1);
			
			// Updating the product
			synchronized(mStorage) {
				mRemain = mStorage.buyProduct(mProduct, 1);
			}
			
			// Notify all listeners that product was updated or removed
			synchronized(mHandler) {
				mHandler.post(new Runnable() {
					@Override
					public void run() {
						UUID id = mProduct.getId();
						
						if (mRemain > 0) {
							for (IUpdatable listener : mListeners) {
								listener.onPurchased(id, mRemain);
							}
						}
						else {
							for (IUpdatable listener : mListeners) {
								listener.onRemoved(id);
							}
						}
							
					}
					
				});
			}
			
		}
		
	}
	
	/**
	 * Runnable class for adding 
	 *
	 */
	private class RunnableAdd implements Runnable {
		// Link to the global mListeners
		private List<IUpdatable> mListeners = ProductManager.this.mListeners;
		// Link to the global mStorage
		private Storage mStorage = ProductManager.this.mStorage;
		// Processed product
		private Product mProduct = null;
		
		/**
		 * Creates new runnable object
		 * 
		 * @param product
		 * Processed product
		 * 
		 */
		public RunnableAdd(Product product) {
			this.mProduct = new Product(product);
		}
		
		@Override
		public void run() {
			// Simulation of long process
			try {
				Thread.sleep(DELAY_LONG);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			boolean isOk = false;
			
			// Adding the product
			synchronized(mStorage) {
				isOk = mStorage.addProduct(mProduct);
			}
			
			// Notify all listeners that product was added
			if (isOk) {
				synchronized(mHandler) {
					mHandler.post(new Runnable() {
						@Override
						public void run() {
							for (IUpdatable listener : mListeners) {
								listener.onUpdated(mProduct);
							}
							
						}
						
					});
				}
			}
			
		}
		
	}
	
	/**
	 * Runnable class for updating 
	 *
	 */
	private class RunnableUpdate implements Runnable {
		// Link to the global mListeners
		private List<IUpdatable> mListeners = ProductManager.this.mListeners;
		// Link to the global mStorage
		private Storage mStorage = ProductManager.this.mStorage;
		// Processed product
		private Product mProduct = null;
		
		/**
		 * Creates new runnable object
		 * 
		 * @param product
		 * Processed product
		 * 
		 */
		public RunnableUpdate(Product product) {
			this.mProduct = new Product(product);
		}
		
		@Override
		public void run() {
			// Simulation of long process
			try {
				Thread.sleep(DELAY_LONG);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			boolean isOk = false;
			
			// Adding the product
			synchronized(mStorage) {
				isOk = mStorage.updateProduct(mProduct);
			}
			
			// Notify all listeners that product was added
			if (isOk) {
				synchronized(mHandler) {
					mHandler.post(new Runnable() {
						@Override
						public void run() {
							for (IUpdatable listener : mListeners) {
								listener.onUpdated(mProduct);
							}
							
						}
					
					});
				}
			}
			
		}
		
	}
	
	/**
	 * Runnable class for removing 
	 *
	 */
	private class RunnableRemove implements Runnable {
		// Link to the global mListeners
		private List<IUpdatable> mListeners = ProductManager.this.mListeners;
		// Link to the global mStorage
		private Storage mStorage = ProductManager.this.mStorage;
		// ID of the product
		private UUID mId = null;
		
		/**
		 * Creates new runnable object
		 * 
		 * @param product
		 * Processed product
		 * 
		 */
		public RunnableRemove(UUID id) {
			this.mId = id;
		}
		
		@Override
		public void run() {
			// Notify all listeners that product was added
			synchronized(mHandler) {
				mHandler.post(new Runnable() {
					@Override
					public void run() {
						for (IUpdatable listener : mListeners) {
							listener.onRemoved(mId);
						}
						
					}
					
				});
			}
			
			// Simulation of long process
			try {
				Thread.sleep(DELAY_LONG);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			// Adding the product
			synchronized(mStorage) {
				mStorage.removeProduct(mId);
			}
			
		}
		
	}
	
	/**
	 * Creates new product manager
	 * 
	 * @param storage
	 * Storage object
	 * 
	 */
	public ProductManager(Storage storage) {
		storage.setOnRefreshListener(this.mRefreshListener);
		this.mStorage = storage;
	}

	/**
	 * Adds new update listener if it not exists 
	 * 
	 * @param listener
	 * Update listener
	 * 
	 */
	public void addListener(IUpdatable listener) {
		if (!this.mListeners.contains(listener)) {
			this.mListeners.add(listener);
		}
	}

	/**
	 * Removes update listener if it exists
	 * 
	 * @param listener
	 */
	public void removeListener(IUpdatable listener) {
		if (this.mListeners.contains(listener)) {
			this.mListeners.remove(listener);
		}
	}
	
	@Override
	public int buyProduct(Product product, int count) {
		// Run thread for buying the product 
		RunnableBuy runnable = new RunnableBuy(product);
		new Thread(runnable).start();
		
		return 0;
	}

	@Override
	public boolean addProduct(Product product) {
		// Getting product amount
		int amount = product.getAmount();
		
		// Return if necessary
		if (amount <= 0) {
			return false;
		}
		
		// Run thread for adding the product 
		RunnableAdd runnable = new RunnableAdd(product);
		new Thread(runnable).start();
		
		return true;
	}

	@Override
	public boolean updateProduct(Product product) {
		// Getting product amount
		int amount = product.getAmount();
		
		// Remove the product if necessary
		if (amount <= 0) {
			return removeProduct(product.getId());
		}
		
		// Run thread for updating the product 
		RunnableUpdate runnable = new RunnableUpdate(product);
		new Thread(runnable).start();
		
		return true;
	}

	@Override
	public boolean removeProduct(UUID id) {
		// Run thread for removing the product 
		RunnableRemove runnable = new RunnableRemove(id);
		new Thread(runnable).start();
		
		return true;
	}
	
}
